/*
 * JsonRpcController - a abstract controller for Spring and JSON-RPC-JAVA integration
 *
 * Copyright Aldrin Edison Baroi 2006
 *
 * Aldrin Edison Baroi <aldrin91011@yahoo.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */
package org.charless.spring.jsonrpc;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import com.metaparadigm.jsonrpc.JSONRPCBridge;
import com.metaparadigm.jsonrpc.JSONRPCServlet;

/**
 * <p>
 * <b>Implementation Notes:</b><br/><br/>
 * <ul>
 * <li>First create the interface classes for the AJAX services to be exposed.</li>
 * <li>Create the controller by extending <b>JsonRpcController</b> that
 * implements the AJAX service interfaces.</li>
 * <li>Configure the controller as described below in the 'Configuration
 * Options' section.</li>
 * </ul>
 * </p>
 * <p>
 * <b>Configuration Options</b><br/><br/> <b>Option 1:</b> (Exposing only
 * one service interface)<br/>
 *
 * <pre>
 *        &lt;bean name=&quot;[Controller Name]&quot; class=&quot;[Controller Class]&quot;&gt;
 *          &lt;property name=&quot;ajaxServiceName&quot; value=&quot;[Ajax Service Name]&quot; /&gt;
 *          &lt;property name=&quot;ajaxServiceInterface&quot; value=&quot;[Ajax Service Interface]&quot; /&gt;
 *        &lt;/bean&gt;
 * </pre>
 *
 * <b>Option 2:</b> (Exposing more than one service interfaces)<br/>
 *
 * <pre>
 *        &lt;bean name=&quot;[Controller Name]&quot; class=&quot;[Controller Class]&quot;&gt;
 *          &lt;property name=&quot;ajaxServices&quot;&gt;
 *            &lt;map&gt;
 *              &lt;entry key=&quot;[Ajax Service Name 1]&quot; value=&quot;[Ajax Service Interface 1]&quot; /&gt;
 *              &lt;entry key=&quot;[Ajax Service Name 2]&quot; value=&quot;[Ajax Service Interface 2]&quot; /&gt;
 *            &lt;/map&gt;
 *          &lt;/property&gt;
 *        &lt;/bean&gt;
 * </pre>
 *
 * </p>
 *
 * @author Aldrin E Baroi
 *
 */
public abstract class JsonRpcController extends AbstractController implements
        DisposableBean, InitializingBean, BeanNameAware
{
    private Log logger = LogFactory.getLog(JsonRpcController.class);
    private ThreadLocal<HttpServletRequest>  httpRequest  = new ThreadLocal<HttpServletRequest>();
    private ThreadLocal<HttpServletResponse> httpResponse = new ThreadLocal<HttpServletResponse>();
    private String jsonRpcControllerName;
    private Servlet jsonRpcServlet;
    private String ajaxServiceName;
    private Class<?> ajaxServiceInterface;
    private Map<String,String> ajaxServices;
    private Map<String,Class<?>> ajaxServicesList;
    private boolean ajaxServicesRegistered;

    /**
    *
    */
    public JsonRpcController()
    {
        super();
    }

    /*
    * (non-Javadoc)
    *
    * @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal(javax.servlet.http.HttpServletRequest,
    *      javax.servlet.http.HttpServletResponse)
    */
    @Override
    protected final ModelAndView handleRequestInternal(HttpServletRequest req,
            HttpServletResponse res) throws Exception
    {
        httpRequest.set(req);
        httpResponse.set(res);
        if (!ajaxServicesRegistered) {
            // Register to global bridge
            registerAjaxServices(req);
        }
        HttpSession session = req.getSession();
        JSONRPCBridge json_bridge = null;
        json_bridge = (JSONRPCBridge) session.getAttribute("JSONRPCBridge");
        if (json_bridge == null) {
            json_bridge = new JSONRPCBridge();
            session.setAttribute("JSONRPCBridge",json_bridge);
            if (logger.isDebugEnabled()) {
                logger.debug("A new local JSONRPCBridge \'"+json_bridge.toString()+"\' has been created for session with id="+session.getId());
            }
            registerSessionAjaxServices(json_bridge);
        }

        jsonRpcServlet.service(req, res);
        return null;
    }

    /**
    *
    * @param req
    * @param res
    */
    private final void registerAjaxServices(HttpServletRequest req)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Registering: Ajax Services for ["
                    + jsonRpcControllerName + "].");
        }
        // Using global bridge
        JSONRPCBridge j = JSONRPCBridge.getGlobalBridge();

        //TODO: optimization (Map.Entry)
        for (Iterator<String> i = ajaxServicesList.keySet().iterator(); i.hasNext();)
        {
            String   _serviceProviderName = /*(String)*/ i.next();
            Class<?> _serviceProviderInterface = /*(Class)*/ ajaxServicesList
                    .get(_serviceProviderName);
            j.registerObject(_serviceProviderName, this,
                    _serviceProviderInterface);
        }
        ajaxServicesRegistered = true;
        if (logger.isDebugEnabled())
        {
            logger.debug("Registered : Ajax Services for ["
                    + jsonRpcControllerName + "].");
        }

    }

    private final void registerSessionAjaxServices(JSONRPCBridge bridge) throws Exception
    {
        if (logger.isDebugEnabled()) {
            logger.debug("Registering: Ajax Services to Session bridge \'"+bridge.toString()+"\'");
        }
        registerSessionAjaxServicesImpl(bridge);
    }

    protected void registerSessionAjaxServicesImpl(JSONRPCBridge bridge) throws Exception {

    }

    /*
    * (non-Javadoc)
    *
    * @see org.springframework.beans.factory.DisposableBean#destroy()
    */
    @Override
    public void destroy() throws Exception
    {
        jsonRpcServlet.destroy();
        jsonRpcServlet = null;
    }

    /**
    *
    */
    @Override
    public final void setBeanName(String beanName)
    {
        this.jsonRpcControllerName = beanName;
    }

    /**
    *
    * @param serviceProviderName
    */
    public final void setAjaxServiceName(String serviceProviderName)
    {
        this.ajaxServiceName = serviceProviderName;
    }

    /**
    *
    * @param serviceProviderInterface
    */
    public final void setAjaxServiceInterface(Class<?> serviceProviderInterface)
    {
        this.ajaxServiceInterface = serviceProviderInterface;
    }

    /**
    *
    * @param serviceProviders
    */
    public final void setAjaxServices(Map<String,String> serviceProviders)
    {
        this.ajaxServices = serviceProviders;
    }

    /*
    * (non-Javadoc)
    *
    * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
    */
    @Override
    public final void afterPropertiesSet() throws Exception
    {
        if (this.ajaxServiceName == null && this.ajaxServiceInterface == null
                && this.ajaxServices == null)
        {
            IllegalArgumentException e = new IllegalArgumentException(
                    "Either ajaxServiceName and ajaxServiceInterface or, ajaxServices must be set.");
            logger.fatal(e);
            throw e;
        }
        this.ajaxServicesList = new HashMap<String,Class<?>>();
        if (this.ajaxServices == null)
        {
            if (this.ajaxServiceName == null
                    || this.ajaxServiceInterface == null)
            {
                IllegalArgumentException e = new IllegalArgumentException(
                        "Both ajaxServiceName and ajaxServiceInterface must be set.");
                logger.fatal(e);
                throw e;
            }
            if (!this.ajaxServiceInterface.isInterface())
            {
                IllegalArgumentException e = new IllegalArgumentException("["
                        + ajaxServiceInterface.getName() + "] "
                        + "ajaxServiceInterface must be of type interface.");
                logger.fatal(e);
                throw e;
            }
            this.ajaxServicesList.put(this.ajaxServiceName,
                    this.ajaxServiceInterface);
        }
        else
        {
            //TODO: optimization (Map.Entry)
            for (Iterator<String> i = ajaxServices.keySet().iterator(); i.hasNext();)
            {
                String _serviceProviderName = /*(String)*/ i.next();
                String _serviceProviderClassName = /*(String)*/ ajaxServices
                        .get(_serviceProviderName);
                Class<?> _serviceProviderInterface = Class
                        .forName(_serviceProviderClassName);
                if (!_serviceProviderInterface.isInterface())
                {
                    throw new IllegalArgumentException("["
                            + _serviceProviderClassName + "] "
                            + "ajaxServiceInterface must be of type interface.");
                }
                this.ajaxServicesList.put(_serviceProviderName,
                        _serviceProviderInterface);
            }
        }
        JsonRpcServletContext ctx = new JsonRpcServletContext(
                getServletContext());
        this.jsonRpcServlet = (Servlet) JSONRPCServlet.class.newInstance();
        this.jsonRpcServlet.init(ctx.getJsonRpcServletConfg());
    }

    public final HttpServletRequest getHttpServletRequest()
    {
        return httpRequest.get();
    }

    public final HttpServletResponse getHttpServletResponse()
    {
        return httpResponse.get();
    }
}
